﻿Shader "Unity Shaders Book/Chapter 11/Billboard"
{
    Properties
    {
        // 广告牌显示的透明问题
        _MainTex ("Texture", 2D) = "white" {}
        // 整体颜色
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        // 法线固定还是向上的方向固定（约束垂直方向的程度）
        _VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1
    }

    SubShader
    {
        Tags
        {
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
            // 基于模型空间的顶点动画需要禁用批处理
            // 使用物体的模型空间的位置作为锚点进行计算
            "DisableBatching" = "True"
        }
        LOD 100

        Pass
        {
            Tags {"LightMode" = "ForwardBase"}

            // 关闭深度写入
            ZWrite Off
            // 开启并设置混合模式
            Blend SrcAlpha OneMinusSrcAlpha
            // 关闭剔除（让广告牌两面都能显示）
            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            float _VerticalBillboarding;

            v2f vert (appdata v)
            {
                v2f o;

                // >>>>> 得到法线方向
                // 选择模型空间的原点作为广告牌的锚点
                float3 center = float3(0, 0, 0);
                // 利用内置变量获取模型空间下的视角位置
                float3 viewer = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));

                // 期望的法线方向
                float3 normalDir = viewer - center;
                // 如果_VerticalBillboarding为1，法线的期望方向为视角方向；
                // 如果_VerticalBillboarding为0，法线的期望方向Y值为0，则向上的方向固定为（0,1,0）;
                normalDir.y = normalDir.y * _VerticalBillboarding;
                // 归一化得到单位矢量
                normalDir = normalize(normalDir);

                // >>>>> 得到向上的方向
                // 为了防止法线方向和向上方向平行（如果平行，叉积得到的结果将是错误的），
                // 对法线方向的Y分量进行判断，以得到合适的向上方向
                float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);

                // >>>>> 得到向右的方向
                // 根据法线方向和粗略的向上方向得到向右方向，并进行归一化
                float3 rightDir = normalize(cross(upDir, normalDir));
                // 此时向上的方向时不精准的，需要根据精准的法线方向和向右的方向得到最后向上的方向
                upDir = normalize(cross(normalDir, rightDir));

                // 得到当前顶点相对锚点的偏移
                float3 centerOffs = v.vertex.xyz - center;
                // 根据正交基矢量，得到新的顶点位置
                float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir.z * centerOffs.z;
                // 把模型空间的顶点位置变换到裁剪空间中
                o.pos = UnityObjectToClipPos(float4(localPos, 1));

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= _Color.rgb;
                return c;
            }
            ENDCG
        }
    }

    Fallback "Transparent/VertexLit"
}
